﻿using System;

namespace HYGE.AsyncCommand
{
    /// <summary>
    /// Func{TResult}デリゲートを使用するIAsyncCommand実装。
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    public class AsyncFuncCommand<TResult> : AsyncCommandBase
    {
        #region Fields

        #region executeAsync
        /// <summary>
        /// executeAsyncデリゲート。
        /// </summary>
        private Func<TResult> executeAsync = null;
        #endregion

        #region onCompleted
        /// <summary>
        /// onCompletedデリゲート。
        /// </summary>
        private Action<int, TResult> onCompleted = null;
        #endregion

        #region onError
        /// <summary>
        /// onErrorデリゲート。
        /// </summary>
        private Action<int, AggregateException> onError = null;
        #endregion

        #region describeJob
        /// <summary>
        /// describeJobデリゲート。
        /// </summary>
        private Func<string> describeJob = null;
        #endregion

        #region canExecute
        /// <summary>
        /// canExecuteデリゲート。
        /// </summary>
        private Func<bool> canExecute = null;
        #endregion

        #endregion

        #region Constructors

        #region Default
        /// <summary>
        /// デフォルトコンストラクタ。
        /// </summary>
        /// <param name="executeAsync"></param>
        /// <param name="onCompleted"></param>
        /// <param name="onError"></param>
        /// <param name="describeJob"></param>
        /// <param name="canExecute"></param>
        public AsyncFuncCommand(Func<TResult> executeAsync, Action<int, TResult> onCompleted, Action<int, AggregateException> onError = null, Func<string> describeJob = null, Func<bool> canExecute = null)
        {
            //引数チェック
            if (executeAsync == null)
                throw (new ArgumentNullException("executeAsync"));
            if (onCompleted == null)
                throw (new ArgumentNullException("onCompleted"));

            //デリゲート登録
            this.executeAsync = executeAsync;
            this.onCompleted = onCompleted;
            this.onError = onError;
            this.describeJob = describeJob;
            this.canExecute = canExecute;
        }
        #endregion

        #endregion

        #region OverrideMethods

        #region CanExecute
        /// <summary>
        /// CanExecuteのオーバーライド。
        /// </summary>
        /// <param elementName="parameter"></param>
        /// <returns></returns>
        protected override bool CanExecute(object parameter)
        {
            return (this.canExecute == null ? true : this.canExecute());
        }
        #endregion

        #region Execute
        /// <summary>
        /// Executeのオーバーライド。
        /// </summary>
        /// <param elementName="parameter"></param>
        protected override void Execute(object parameter)
        {
            //IAsyncJob生成
            IAsyncJob job = new AsyncFuncJob<TResult>(this.executeAsync, this.onCompleted, this.onError, this.describeJob == null ? "" : this.describeJob());
                
            //JobCreatedイベント発行
            this.OnJobCreated(job);

            //IAsyncJob実行
            if (this.Scheduler == null)
                job.Start();
            else
                job.Start(this.Scheduler);
        }
        #endregion

        #endregion

        #region PublicMethods

        #region CanExecute
        /// <summary>
        /// 公開用のCanExecute。CanExecute()にシグネチャ変更。
        /// </summary>
        /// <param elementName="parameter"></param>
        /// <returns></returns>
        public bool CanExecute()
        {
            return (this.CanExecute(null));
        }
        #endregion

        #region Execute
        /// <summary>
        /// 公開用のExecute。Execute()にシグネチャ変更。
        /// </summary>
        public void Execute()
        {
            this.Execute(null);
        }
        #endregion

        #endregion
    }

    /// <summary>
    /// Func{T,TResult}デリゲートを使用するIAsyncCommand実装。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    public class AsyncFuncCommand<T, TResult> : AsyncCommandBase
    {
        #region Fields

        #region executeAsync
        /// <summary>
        /// executeAsyncデリゲート。
        /// </summary>
        private Func<T, TResult> executeAsync = null;
        #endregion

        #region onCompleted
        /// <summary>
        /// onCompletedデリゲート。
        /// </summary>
        private Action<int, TResult> onCompleted = null;
        #endregion

        #region onError
        /// <summary>
        /// onErrorデリゲート。
        /// </summary>
        private Action<int, AggregateException> onError = null;
        #endregion

        #region onCanceled
        /// <summary>
        /// onCanceledデリゲート。
        /// </summary>
        private Action<int> onCanceled = null;
        #endregion

        #region describeJob
        /// <summary>
        /// describeJobデリゲート。
        /// </summary>
        private Func<T, string> describeJob = null;
        #endregion

        #region canExecute
        /// <summary>
        /// canExecuteデリゲート。
        /// </summary>
        private Func<T, bool> canExecute = null;
        #endregion

        #endregion

        #region Constructors

        #region Default
        /// <summary>
        /// デフォルトコンストラクタ。
        /// </summary>
        /// <param name="executeAsync"></param>
        /// <param name="onCompleted"></param>
        /// <param name="onError"></param>
        /// <param name="onCanceled"></param>
        /// <param name="describeJob"></param>
        /// <param name="canExecute"></param>
        public AsyncFuncCommand(Func<T, TResult> executeAsync, Action<int, TResult> onCompleted, Action<int, AggregateException> onError = null, Action<int> onCanceled = null, Func<T, string> describeJob = null, Func<T, bool> canExecute = null)
        {
            //引数チェック
            if (executeAsync == null)
                throw (new ArgumentNullException("executeAsync"));
            if (onCompleted == null)
                throw (new ArgumentNullException("onCompleted"));

            //デリゲート登録
            this.executeAsync = executeAsync;
            this.onCompleted = onCompleted;
            this.onError = onError;
            this.onCanceled = onCanceled;
            this.describeJob = describeJob;
            this.canExecute = canExecute;
        }
        #endregion

        #endregion

        #region OverrideMethods

        #region CanExecute
        /// <summary>
        /// CanExecuteのオーバーライド。
        /// </summary>
        /// <param elementName="parameter"></param>
        /// <returns></returns>
        protected override bool CanExecute(object parameter)
        {
            if (this.canExecute == null)
                return (true);
            else
                return (parameter == null ? this.canExecute(default(T)) : this.canExecute((T)parameter));
        }
        #endregion

        #region Execute
        /// <summary>
        /// Executeのオーバーライド。
        /// </summary>
        /// <param elementName="parameter"></param>
        protected override void Execute(object parameter)
        {
            //IAsyncJob生成
            string description = string.Empty;
            if (this.describeJob != null)
                description = parameter == null ? this.describeJob(default(T)) : this.describeJob((T)parameter);
            IAsyncJob job = new AsyncFuncJob<T, TResult>(this.executeAsync, parameter == null ? default(T) : (T)parameter, this.onCompleted, this.onError, this.onCanceled, description);
            
            //JobCreatedイベント発行
            this.OnJobCreated(job);

            //IAsyncJob実行
            if (this.Scheduler == null)
                job.Start();
            else
                job.Start(this.Scheduler);
        }
        #endregion

        #endregion

        #region PublicMethods

        #region CanExecute
        /// <summary>
        /// 公開用のCanExecute。CanExecute(T)にシグネチャ変更。
        /// </summary>
        /// <param elementName="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(T parameter)
        {
            return (this.CanExecute((object)parameter));
        }
        #endregion

        #region Execute
        /// <summary>
        /// 公開用のExecute。Execute(T)にシグネチャ変更。
        /// </summary>
        public void Execute(T parameter)
        {
            this.Execute((object)parameter);
        }
        #endregion

        #endregion
    }
}
